import {
  Component,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  TemplateRef,
  ElementRef,
  ChangeDetectorRef,
  SimpleChanges,
  OnInit,
  OnChanges,
  AfterViewInit,
  ViewChild,
  OnDestroy,
} from '@angular/core';

import { InputBoolean, TypeUtils, InputNumber, ResizeObserverService } from '../core/api';
import { ProgressStatus, ProgressStatusEnum, ProgressType, ProgressTypeEnum } from './interface';

@Component({
  selector: 'lv-progress, [lv-progress]',
  exportAs: 'lvProgress',
  templateUrl: './progress.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class.lv-progress]': 'true',
    '[class.lv-progress-top-label]': "lvLabelPosition === 'top'",
    '[class.lv-progress-small]': "lvSize === 'small'",
    '[class.lv-progress-innerlabel]': 'lvInnerLabel',
    '[class.lv-progress-line]': 'lvType === typeEnum.LINE',
    '[class.lv-progress-circle]': 'lvType === typeEnum.CIRCLE',
    '[class.lv-progress-dashed]': 'lvType === typeEnum.DASHED',
    '[class.lv-progress-error]': 'status === statusEnum.ERROR',
    '[class.lv-progress-success]': 'status === statusEnum.SUCCESS',
    '[class.lv-progress-active]': 'status === statusEnum.ACTIVE',
  },
})
export class ProgressComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
  @Input() lvDecimals;

  @Input() lvColors: any[] = [];

  @Input() lvStatus: ProgressStatus;

  @Input() @InputNumber() lvValue = 0;

  @Input() @InputNumber() lvWidth = 0;

  @Input() lvType: ProgressType = 'line';

  @Input() @InputBoolean() lvShowLabel = true;

  @Input() @InputBoolean() lvInnerLabel = false;

  @Input() lvSize: 'small' | 'default' = 'default';

  @Input() lvExtra: TemplateRef<{ $implicit: any }>;

  @Input() lvLabel: string | TemplateRef<void> = null;

  @Input() lvLabelPosition: 'top' | 'right' = 'right';

  @Input() lvMethod: 'round' | 'floor' | 'ceil' = 'round';

  @ViewChild('dashedWrapper', { static: false }) dashedWrapper: ElementRef;
  _lvValue = 0;

  public bgPathStyle;
  _progressHeight = 8;
  public radius = 100;
  public circleBgPath;
  public handlePathStyle;
  public handleColor = '';
  public circleHandlePath;

  _smallProgressHeight = 6;
  public circleBgWidth = 3;
  public typeUtils = TypeUtils;
  public containerWidth = '100%';
  public viewBox = '0 0 200 200';
  status: ProgressStatus = 'normal';
  public typeEnum = ProgressTypeEnum;
  public dashedWidth: number | string;

  public statusEnum = ProgressStatusEnum;
  public iconError = 'lv-icon-status-error';
  public iconSuccess = 'lv-icon-status-succeed';
  public circleHandleWidth = this._progressHeight;

  public viewPort = {
    width: '100%',
    height: this._progressHeight,
  };

  public dashedBg = {
    path: '',
    style: {
      strokeDasharray: 1,
    },
  };

  public dashedHandle = {
    path: '',
    style: { stroke: '', strokeDasharray: 1 },
  };

  private _dashedObserver;

  constructor(
    public cdr: ChangeDetectorRef,
    public el: ElementRef,
    private resizeObserver: ResizeObserverService,
  ) {}

  ngOnDestroy(): void {
    this._dashedObserver?.unobserve();
  }

  ngOnInit(): void {
    this.setStatus();
    this.setWidth();
    this.resetColor();
    if (this.lvType === this.typeEnum.CIRCLE) {
      this.resetCircle();
    }
  }

  setStatus(): void {
    if (this.lvStatus) {
      this.status = this.lvStatus;
    } else {
      this.status = this.lvValue === 100 ? this.statusEnum.SUCCESS : this.statusEnum.NORMAL;
    }
  }

  setWidth(): void {
    if (this.lvType === this.typeEnum.CIRCLE) {
      this.containerWidth = this.lvWidth !== 0 ? `${this.lvWidth}px` : '200px';
    } else {
      this.containerWidth = this.lvWidth !== 0 ? `${this.lvWidth}px` : '100%';
    }
  }

  resetColor(): void {
    const colorArray = Array.from(this.lvColors);
    for (let i = colorArray.length - 1; i >= 0; i--) {
      if (this.lvValue >= colorArray[i][0]) {
        this.handleColor = colorArray[i][1];
        break;
      } else {
        this.handleColor = '';
      }
    }
  }

  ngAfterViewInit(): void {
    Promise.resolve().then((): void => {
      if (this.lvType === this.typeEnum.DASHED) {
        this.resetDashed();
        this.cdr.markForCheck();
      }
    });

    if (this.dashedWrapper) {
      this._dashedObserver = this.resizeObserver.observe(this.dashedWrapper.nativeElement, (): void => {
        this.resetDashed();
        this.cdr.markForCheck();
      });
    }
  }

  resetDashed(): void {
    if (this.lvInnerLabel) {
      this.dashedWidth = 16;
      this.viewPort.height = 16;
    }

    const container = this.dashedWrapper?.nativeElement;
    const width = isNaN(container?.clientWidth) ? this.radius * 2 : container?.clientWidth;
    this.viewBox = `0 0 ${width} ${this.dashedWidth}`;

    this.dashedBg.path = `M 0 0 L ${width} 0`;
    this.dashedHandle.path = `M 0 0 L ${(this.lvValue / 100) * width} 0`;
    this.dashedBg.style = {
      strokeDasharray: 2,
    };
    this.dashedHandle.style = {
      stroke: this.handleColor,
      strokeDasharray: 2,
    };
  }

  /**
   * 按需保留小数位
   * @param decimals 保留小数点几位数，默认0
   * @param method 保留小数的方式，默认四舍五入
   */
  NumberToFixed(value, decimals = 0, method = 'round'): any {
    const k = Math.pow(10, decimals);
    let _value;
    switch (method) {
      // 取向下整
      case 'floor':
        _value = Math.floor(value * k) / k;
        break;
      // 向上取整
      case 'ceil':
        _value = Math.ceil(value * k) / k;
        break;
      // 四舍五入
      default:
        _value = Math.round(value * k) / k;
    }

    if (decimals === 0) {
      return _value;
    } else {
      const values = _value.toString().split('.');
      const v_int = values[0];
      let v_float = values[1] || '0';
      for (let i = v_float.length; i < decimals; i++) {
        v_float = `${v_float}0`;
      }
      return `${v_int}.${v_float}`;
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    const flag = changes.lvWidth || changes.lvValue || changes.lvSize || changes.lvColors || changes.lvStatus;
    if (flag) {
      this.lvValue = Math.max(0, Math.min(100, Number(changes.lvValue ? changes.lvValue.currentValue : this.lvValue)));
      this._lvValue = !this.lvDecimals
        ? this.lvValue
        : this.NumberToFixed(this.lvValue, this.lvDecimals, this.lvMethod);

      if (this.lvSize === 'small') {
        this.circleHandleWidth = this._smallProgressHeight;
        this.dashedWidth = this._smallProgressHeight;
        this.viewPort.height = this._smallProgressHeight;
      } else {
        this.circleHandleWidth = this._progressHeight;
        this.dashedWidth = this._progressHeight;
        this.viewPort.height = this._progressHeight;
      }
      this.setStatus();
      this.setWidth();
      this.resetColor();
      if (this.lvType === this.typeEnum.CIRCLE) {
        this.resetCircle();
      }
      if (this.lvType === this.typeEnum.DASHED) {
        this.resetDashed();
      }

      this.cdr.markForCheck();
    }
  }

  resetCircle(): void {
    this.iconSuccess = 'lv-icon-ok';
    this.iconError = 'lv-icon-close';

    this.radius = this.lvWidth !== 0 ? this.lvWidth * 0.5 : this.radius;
    this.viewBox = `0 0 ${this.radius * 2} ${this.radius * 2}`;
    const _bgRadius = this.radius - this.circleBgWidth * 0.5;
    const _handleRadius = this.radius - this.circleHandleWidth * 0.5;
    this.circleBgPath = `M ${this.radius} ${this.radius} m 0 -${_bgRadius} a ${_bgRadius} -${_bgRadius} 0 1 1 0 ${
      _bgRadius * 2
    } a -${_bgRadius} ${_bgRadius} 0 1 1 0 -${_bgRadius * 2}`;
    this.circleHandlePath = `M ${this.radius} ${this.radius} m 0 -${_handleRadius} a ${_handleRadius} -${_handleRadius} 0 1 1 0 ${
      _handleRadius * 2
    } a -${_handleRadius} ${_handleRadius} 0 1 1 0 -${_handleRadius * 2}`;

    const girth = Math.PI * 2 * _handleRadius;
    this.bgPathStyle = {
      strokeDasharray: `${girth}px ${girth}px`,
      strokeDashoffset: `${-(this.lvValue / 100) * girth - this.lvValue / 10 - 2}px`,
    };
    this.handlePathStyle = {
      stroke: this.handleColor,
      strokeDasharray: `${(this.lvValue / 100) * girth}px ${girth}px`,
    };
  }
}
